<?php
/**
 * Description of basic_model_tree
 *
 * @author:	qfox
 * @package:	core
 * @copyright:	(c) 2010, alex <qfox@ya.ru> yaroshevich aka alex_ez. All rights reserved
 * @see:		core/classes/basic_model.php
 * @link:		http://docs.qfox.ru/model_tree
 * @created:	19.04.2010, 15:14:48
 */
abstract class basic_model_tree extends model {
	
	/**
	 * service variables
	 */
	protected $parent_id_key = 'parent_id';
	protected $tree_path_key = 'tree_path';
	private $__last_get_by = null;

	protected $sort_key = 'order';
	protected $sort_path_key = 'order_path';

	protected $default_value = null;

	/**
	 * Constructor
	 *
	 * Creates new object and set default values for empty service variables
	 */
	public function __construct( $value = null )
	{
		parent::__construct( );

		$this ->default_value = $value;
	}


	/**
	 * Getters
	 */

	public function &mixed_tree_by( $p = null, $o = null, $cacheTime = -1 )
	{
		return $this->mixed_tree_fby( '*', $p, $o, $cacheTime );
	}

	public function &mixed_tree_fby( $f = null, $p = null, $o = null, $cacheTime = -1 )
	{
		$sk = $this->sort_key;
		$spk = $this->sort_path_key;

		if (!is_null($p) && $p!='') {
			$this->checkup_sql( $p, $o );
			$ids = $this->ids_by($p);

			$tree = $this->mixed_tree_fby($f, null, $o, $cacheTime);
			$subtree = array();
			foreach( array_keys($tree) as $node_id )
				if (in_array($node_id,$ids))
					$subtree[$node_id] =& $tree[$node_id];
			unset($tree);

			$r = $this->catch_them_all( $subtree );
			unset($subtree);
			uasort($r, array($this, $sk ? '_sort_spk_callback' : '_sort_tpk_callback'));

			return $r;
		}

		if (is_null($o))
			$o = 'id+';

		$pk = $this->parent_id_key;
		$tpk = $this->tree_path_key;

		$tree = array();
		$nodes = $this->fby($f, '', $pk.'+,' . $o);

		foreach ($nodes as $node) {
			$tree[$node['id']] = $node;
			$tree[$node['id']][$tpk] = $node[$pk] ? $node[$pk] : '';

			if ($sk)
				$tree[$node['id']][$spk] = $node[$sk] . '-' . $node['id'];
		}

		foreach ( $tree as $node2 ) {
			if ( $node2[$pk] == 0 ) continue;

			if (!isset($tree[$node2[$pk]]['children']))
				$tree[$node2[$pk]]['children'] = array();

			$tree[$node2['id']]['parent'] =& $tree[$node2[$pk]];
			$tree[$node2[$pk]]['children'][$node2['id']] =& $tree[$node2['id']];

			$tree[$node2['id']][$tpk] = trim( $tree[$node2[$pk]][$tpk] . '.' . $node2[$pk], '.' );

			if ($sk)
				$tree[$node2['id']][$spk] = trim( $tree[$node2[$pk]][$spk] . '.' . $tree[$node2['id']][$sk] . '-' . $node2['id'], '.' );
		}

		uasort( $tree, array($this, $sk ? '_sort_spk_callback' : '_sort_tpk_callback') );

		return $tree;
	}

	public function _sort_spk_callback($a,$b) {
		return strnatcmp( $a[$this->sort_path_key], $b[$this->sort_path_key] );
	}

	public function _sort_tpk_callback($a,$b) {
		return strnatcmp( trim($a[$this->tree_path_key].".".$a["id"],"."), trim($b[$this->tree_path_key].".".$b["id"],".") );
	}

	public function get( $id, $cacheTime = -1 )
	{
		$r = parent::get( $id, $cacheTime )	;

		$this ->__last_get_by = array( array( 'id' => $id ), null, null );
		return $r;
	}

	public function node($node_id) {

		$tree = $this->mixed_tree_by();
		$node = $this->find_node($tree, $node_id);

		return $node;
	}

	public function mixed_tree( $node_id ) {
		$node = $this->node( $node_id );
		echo __METHOD__;var_dump( $this->catch_them_all( array( $node ) ) );die;
	}

	public function remove($id) {
		$node = $this->node($id);

		if($node !== false)
			return $this->remove_recursive(array($node));

		return false;
	}

	/*public function by( $p = null, $o = null, $l = null, $cacheTime = -1 )
	{
		$this ->checkup_sql( $p, $o, $l );

		$sql = $this ->make_sql( $this ->table, '*', $p, $o, $l );

		$r = $this ->db ->_list( $sql, 'id' );
		$r = array_filter( $r );
		$this ->__found_rows = $this->db ->_value( "SELECT FOUND_ROWS();" );

		$this ->__last_get_by = array( $p, $o, $l );
		return $r;
	}*/


	/**
	 * Tree blowjob
	 */
	public function move_node($node_id, $target_id) {

		$can_move = false;

		if(!$this->ancestor_of($node_id, $target_id)) {

			// check both nodes exists
			$rslt = $this->by(array('id' => compact('node_id', 'target_id')), '', 2);
			$check_ids = array($node_id);

			if($target_id > 0)
				$check_ids[] = $target_id;

			$rslt = $this->by(array('id' => $check_ids));

			if(sizeof($check_ids) == sizeof($rslt)) {
				$this->edit($node_id, array('parent_id' => $target_id));
				$can_move = true;
			}
		}

		return $can_move;
	}

	public function child_of($child_id, $node_id) {
		return $this->ancestor_of($node_id, $child_id);
	}

	public function ancestor_of($node_id, $child_id) {

		$node = $this->node($node_id);

		if(!empty($node) && isset($node['children']))
			return $this->find_ancestor_of($node['children'], $child_id);

		return false;
	}

	protected function find_ancestor_of($subtree, $node_id) {

		foreach($subtree as $node) {

			if($node['id'] == $node_id)
				return true;

			if(isset($node['children']) && $this->find_ancestor_of($node['children'], $node_id))
				return true;

		}

		return false;
	}

	protected function find_node($subtree, $node_id) {

		foreach($subtree as $node) {

			if($node['id'] == $node_id)
				return $node;

			if(isset($node['children'])){
				$ret = $this->find_node($node['children'], $node_id);
				if($ret !== false)
					return $ret;
			}
		}

		return false;
	}

	protected function remove_recursive($subtree) {
		$deleted = 0;

		foreach($subtree as $node) {

			if(isset($node['children']))
				$deleted += $this->remove_recursive($node['children']);

			if(parent::remove($node['id']))
				$deleted++;
		}

		return $deleted;
	}

	protected function &catch_them_all( &$subtree ) {
		$result = array();

		foreach (array_keys($subtree) as $node_id) {

			$node =& $subtree[$node_id];
			$result[$node_id] =& $node;

			if( isset( $node['children'] ) && is_array( $node['children'] ) && !empty( $node['children'] ) )
				$result += $this->catch_them_all( $node['children'] );
		}

		return $result;
	}



}


